import math
#from numpy.random import *
import random
#import pickle
from D import D1, D2, D3, IC, NC, OC, E

class FNeuron:
    friends = []
    w = []
    cur_sum = 0.0
    storage = 0.0
#------------------------------    
    def __init__(self,netSize):
        self.w = [0.0 for i in range(netSize)]
        self.friends = [0.0 for i in range(netSize)]
        self.cur_sum = 0.0
#------------------------------        
    def AddLink(self,j):
        self.w[j] = random.random()
        self.friends[j] = True
#---------------------------------------------------------------------------------------
class NeuralNet:
    net = []
    delta = []
    epoch = []
    I,N,K = 0, 0, 0
    netSize = 0
    
    speed,eps = 0, 0
    feedforward_prob = 0.75
    feedback_prob = 0.1
    selfconn_prob = 0.0001
    new_link_prob = 0.000001
    max_epoch = 10000
#------------------------------    
    def __init__(self,input_count = 2,neurons_count = 4,output_count = 1,speed = 0.3,eps = 0.03):
        self.I = input_count
        self.N = neurons_count
        self.K = output_count
        
        self.netSize = self.I + self.N + self.K
        self.net = []
        self.speed = speed
        self.eps = eps
        for i in range(self.netSize):
            self.net.append(FNeuron(self.netSize))
            self.epoch.append(FNeuron(self.netSize))
        self.delta = [0.0 for i in range(self.netSize)]
        self.GenerateNet()
#------------------------------        
    def GenerateNet(self):
        for i in range(self.I + self.N):
            for j in range(self.I, self.netSize):
                p = random.random()
                if (p <= self.feedforward_prob and i < j):
                    self.net[i].AddLink(j)
        
        i = self.I + self.N + self.K - 1
        while(i >= 0):
            for j in range(self.I + self.N):
                p = random.random()
                if(p <= self.feedback_prob and i > j):
                    self.net[i].AddLink(j)
            i -= 1
            
        i = self.I
        j = self.I
        while(i < self.I + self.N):
            p = random.random()
            if(p <= self.selfconn_prob and i == j):
                self.net[i].AddLink(j)
            i+=1
            j+=1
#------------------------------
    def Sigmoid(self,x):
        return 1 / (1 + math.exp(-x))
#------------------------------
    def Compute(self, vector):
        for i in range(self.netSize):
            self.net[i].cur_sum = 0.0
            
        for i in range(len(vector)):
            self.net[i].cur_sum = vector[i]
            
        i = self.I
        while(i < self.netSize):
            val = 0.0
            tmp = 0.0
            for j in range(self.netSize):
                if(self.net[j].friends[i] and j < i):
                    val += self.net[j].w[i] * self.net[j].cur_sum
                elif(self.net[j].friends[i] and j >= i):
                    tmp += self.net[j].w[i] * self.net[j].cur_sum
            
            self.net[i].cur_sum = self.Sigmoid(val) + self.net[i].storage
            self.net[i].storage = tmp
            i+=1
            
        result = [0.0 for i in range(self.K)]
        for i in range(self.K):
            result[i] = self.net[self.I + self.N + i].cur_sum
        return result
#------------------------------       
    def BackPropagation(self, vector, optim):
        self.Compute(vector)

        for i in range(self.I + self.N, self.netSize):
            yi = self.net[i].cur_sum
            val = 0.0
            for j in range(self.netSize):
                if(self.net[i].friends[j]):
                    val += self.delta[j] * self.net[i].w[j]
            
            self.delta[i] = ((optim[i - self.I - self.N] - yi) + val) * yi * (1 - yi)
            for j in range(self.netSize):
                if(self.net[j].friends[i]):
                    self.net[j].w[i] += self.speed * self.delta[i] * self.net[j].cur_sum
        
        i = self.I + self.N - 1
        while(i >= self.I):
            val = 0
            for j in range(self.netSize):
                if(self.net[i].friends[j]):
                    val += self.delta[j] * self.net[i].w[j]
            
            yi = self.net[i].cur_sum
            self.delta[i] = val * yi * (1 - yi)
            
            for j in range(self.netSize):
                if(self.net[j].friends[i]):
                    self.net[j].w[i] += self.speed * self.delta[i] * self.net[j].cur_sum
            
            i -= 1
#------------------------------            
    def IsLearned(self,y,opt,epoch=0):
        if epoch > self.max_epoch: return True
        max = 0.0
        n = len(y[0])
        for q in range(len(y)):
            for i in range(n):
                if (abs(opt[q][i] - y[q][i]) > max):
                    max = abs(opt[q][i] - y[q][i])
        if(max >= self.eps):return False
        return True 
#------------------------------ 
    def Training(self, vectors, opt, maxIter=0, exit_on_epochs=False):
        for i in range(len(self.delta)):
            self.delta[i] = 0
        
        y = []
        
        for i in range(len(vectors)):
            y.append(self.Compute(vectors[i]))
        
        cur_epoch = 0
        
        while(not self.IsLearned(y,opt,cur_epoch)):
            cur_epoch += 1
            
            if(cur_epoch >= maxIter and exit_on_epochs):
                return cur_epoch
            
            for i in range(len(vectors)):
                self.BackPropagation(vectors[i], opt[i])
            
            for i in range(len(vectors)):
                y[i] = self.Compute(vectors[i])
            #print cur_epoch,
            self.New_Link()
        print cur_epoch
        print self.Compute(D3[0]), self.Compute(D3[1])
        #self.ToGraph()
        return cur_epoch
#------------------------------ 
    def New_Link(self):
        p = random.randrange(0,1000)/1000000.0
        if(p <= self.new_link_prob):
            i = random.randrange(0, self.I + self.N)
            j = random.randrange(self.I, self.netSize)
            if(not self.net[i].friends[j]):
                self.net[i].AddLink(j)
            """
            try:
                if(not self.net[i].friends[j]):
                    self.net[i].AddLink(j)
            except:
                print '?'
                print 1, i, j
                print 2, self.net, len(self.net)
                print 3, self.net[i]
                print 4, self.net[i].friends, len(self.net[i].friends)
                raise
            """
#------------------------------
    def PrintLinks(self):
        for i in range(self.netSize):
            for j in range(self.netSize):
                if(self.net[i].friends[j]):
                    print "1",
                else:
                    print "0",
            print
#------------------------------
    def ToGraph(self):
        print 'digraph G {'
        print 'rankdir=LR'
        for i in range(self.I):
            print '  in_%d -> %d '%(i,i)
        j = 0
        for i in range(self.I + self.N, self.netSize):
            print '  %d -> out_%d '%(i,j)
            j += 1
        for i in range(self.netSize):
            for j in range(self.netSize):
                if(self.net[i].friends[j]):
                    print '  %d -> %d [label = "%f"]'%(i + 1, j + 1, self.net[i].w[j])
        print '}'
#---------------------------------------------------------------------------------------
# edit here | 
#           V
class Mutate:
    net = []
    qty_net = 3
    result = []
    def __init__(self):  # constructor: creating nets and training this
        self.I = IC
        self.N = NC
        self.K = OC
        self.netSize = self.I + self.N + self.K
        for i in range(self.qty_net+1): 
            self.result.append(NeuralNet(input_count = IC, neurons_count = NC, output_count = OC, eps=E))
            self.net.append(NeuralNet(input_count = IC, neurons_count = NC, output_count = OC, eps=E))
            self.net[i].Training(D1,D2)
    def Mutater(self):  # function mutate all nets, output: two nets, children of all nets
        for k in range(self.qty_net):
            self.result[k+1] = self.net[k]
            self.result[k] = self.net[k+1]
            for i in range(self.netSize):
                for j in range(self.netSize):
                    p = random.random()
                    if(self.net[k].net[i].friends[j] and p <= 0.1): # select link for replace 
                        self.result[k].net[i].w[j] = self.net[k].net[i].w[j]
                        self.result[k].net[i].friends[j] = True
                    if(self.net[k+1].net[i].friends[j] and p <= 0.1): # second net
                        self.result[k+1].net[i].w[j] = self.net[k+1].net[i].w[j]
                        self.result[k+1].net[i].friends[j] = True
        self.qty_net -= 1
        print self.Compute(D3[0]), self.Compute(D3[1]) # compute result of previous vector's
        self.qty_net += 1
        print self.Compute(D3[0]), self.Compute(D3[1]) # result of this vector's
    def Compute(self, vector):     # copypaste from exist net
        for i in range(self.netSize):
            self.result[self.qty_net].net[i].cur_sum = 0.0
            
        for i in range(len(vector)):
            self.result[self.qty_net].net[i].cur_sum = vector[i]
            
        i = self.I
        while(i < self.netSize):
            val = 0.0
            tmp = 0.0
            for j in range(self.netSize):
                if(self.result[self.qty_net].net[j].friends[i] and j < i):
                    val += self.result[self.qty_net].net[j].w[i] * self.result[self.qty_net].net[j].cur_sum
                elif(self.result[self.qty_net].net[j].friends[i] and j >= i):
                    tmp += self.result[self.qty_net].net[j].w[i] * self.result[self.qty_net].net[j].cur_sum
            
            self.result[self.qty_net].net[i].cur_sum = self.Sigmoid(val) + self.result[self.qty_net].net[i].storage
            self.result[self.qty_net].net[i].storage = tmp
            i+=1
            
        result = [0.0 for i in range(self.K)]
        #self.ToGraph()
        for i in range(self.K):
            result[i] = self.result[self.qty_net].net[self.I + self.N + i].cur_sum
        return result
    def Sigmoid(self,x):
        return 1 / (1 + math.exp(-x))
#---------------------------------------------------------------------------------------
net = Mutate()
#vec = [[0,0],[0,1],[1,0],[1,1]]
#res = [[0],[1],[1],[0]]
#print D1[0], D1[1], D1[2], D1[3],

#net.Compute(D1[0]), net.Compute(D1[1]), net.Compute(D1[2]), net.Compute(D1[3])
#net.Training(D1,D2)
#print net.Compute(D2[0])
#print net.Compute(D2[1])
#print net.Compute(D2[2])
net.Mutater()

#print net.Compute(D3[0]), net.Compute(D3[1])
#print D1[0], D1[1], D1[2], D1[3], net.Compute(D1[0]), net.Compute(D1[1]), net.Compute(D1[2]), net.Compute(D1[3])
#net.ToGraph()
#gnuplot

